//
// Created by benxb on 2022/3/13.
//

#ifndef BENXDB_STATUS_THREAD_H
#define BENXDB_STATUS_THREAD_H

#include <iostream>
#include <thread>
#include <chrono>
#include <utility>
#include <cstring>

#include <sys/stat.h>
#include <sys/sysinfo.h>
#include <ctime>
#include <unistd.h>
#include <mutex>
#include <atomic>

namespace benchmark {

// get current process pid
inline int GetCurrentPid() {
    return getpid();
}

// the info line num in /proc/{pid}/status file
#define VMRSS_LINE 22
#define PROCESS_ITEM 14

static const char *get_items(const char *buffer, unsigned int item) {
    // read from buffer by offset
    const char *p = buffer;

    int len = strlen(buffer);
    int count = 0;

    for (int i = 0; i < len; i++) {
        if (' ' == *p) {
            count++;
            if (count == item - 1) {
                p++;
                break;
            }
        }
        p++;
    }

    return p;
}

static inline unsigned long get_cpu_total_occupy() {
    // get total cpu use time

    // different mode cpu occupy time
    unsigned long user_time;
    unsigned long nice_time;
    unsigned long system_time;
    unsigned long idle_time;

    FILE *fd;
    char buff[1024] = {0};

    fd = fopen("/proc/stat", "r");
    if (nullptr == fd)
        return 0;

    fgets(buff, sizeof(buff), fd);
    char name[64] = {0};
    sscanf(buff, "%s %ld %ld %ld %ld", name, &user_time, &nice_time, &system_time, &idle_time);
    fclose(fd);

    return (user_time + nice_time + system_time + idle_time);
}

static inline unsigned long get_cpu_proc_occupy(int pid) {
    // get specific pid cpu use time
    unsigned int tmp_pid;
    unsigned long utime;  // user time
    unsigned long stime;  // kernel time
    unsigned long cutime; // all user time
    unsigned long cstime; // all dead time

    char file_name[64] = {0};
    FILE *fd;
    char line_buff[1024] = {0};
    sprintf(file_name, "/proc/%d/stat", pid);

    fd = fopen(file_name, "r");
    if (nullptr == fd)
        return 0;

    fgets(line_buff, sizeof(line_buff), fd);

    sscanf(line_buff, "%u", &tmp_pid);
    const char *q = get_items(line_buff, PROCESS_ITEM);
    sscanf(q, "%ld %ld %ld %ld", &utime, &stime, &cutime, &cstime);
    fclose(fd);

    return (utime + stime + cutime + cstime);
}

inline float GetCpuUsageRatio(int pid) {
    unsigned long totalcputime1, totalcputime2;
    unsigned long procputime1, procputime2;

    totalcputime1 = get_cpu_total_occupy();
    procputime1 = get_cpu_proc_occupy(pid);

    // FIXME: the 200ms is a magic number, works well
    usleep(200000); // sleep 200ms to fetch two time point cpu usage snapshots sample for later calculation

    totalcputime2 = get_cpu_total_occupy();
    procputime2 = get_cpu_proc_occupy(pid);

    float pcpu = 0.0;
    if (0 != totalcputime2 - totalcputime1)
        pcpu = (procputime2 - procputime1) / float(totalcputime2 - totalcputime1); // float number

    int cpu_num = get_nprocs();
    pcpu *= cpu_num; // should multiply cpu num in multiple cpu machine

    return pcpu;
}

// get specific process physical memeory occupation size by pid (MB)
inline float GetMemoryUsage(int pid) {
    char file_name[64] = {0};
    FILE *fd;
    char line_buff[512] = {0};
    sprintf(file_name, "/proc/%d/status", pid);

    fd = fopen(file_name, "r");
    if (nullptr == fd)
        return 0;

    char name[64];
    int vmrss = 0;
    for (int i = 0; i < VMRSS_LINE - 1; i++)
        fgets(line_buff, sizeof(line_buff), fd);

    fgets(line_buff, sizeof(line_buff), fd);
    sscanf(line_buff, "%s %d", name, &vmrss);
    fclose(fd);

    // cnvert VmRSS from KB to MB
    return vmrss / 1024.0;
}

std::mutex g_status_mtx{};

class StatusThread {
public:
    StatusThread(int pid, std::string identifier, std::string record_dir) :
            pid_(pid), identifier_(std::move(identifier)), record_dir_(std::move(record_dir)){}

    void operator()() {
        float cpu_avg, mem_avg, cpu_max, mem_max;
        cpu_avg = mem_avg = 0;
        cpu_max = mem_max = 0;
        int times = 1;
        std::fstream f(record_dir_ + identifier_ + "_stat.txt", std::ios::trunc);
        while (g_status_mtx.try_lock()) {
            g_status_mtx.unlock();
            float cpu = GetCpuUsageRatio(pid_)*100;
            float mem = GetMemoryUsage(pid_);
            cpu_max = std::max(cpu_max, cpu);
            mem_max = std::max(mem_max, mem);
            cpu_avg += cpu;
            mem_avg += mem;
            f << cpu << " " << mem << std::endl;
            times++;
        }
        cpu_avg /= times;
        mem_avg /= times;

        std::cout << identifier_ << " Status:" << std::endl;
        std::cout << "Avg: cpu: " << cpu_avg << "\t" << "mem: " << mem_avg << std::endl;
        std::cout << "Max: cpu: " << cpu_max << "\t" << "mem: " << mem_max << std::endl;

        std::fstream allf(record_dir_ + identifier_ + "_stat_sum.txt", std::ios::app);
        allf << identifier_ << " Status:" << std::endl;
        allf << "Avg: cpu: " << cpu_avg << "\t" << "mem: " << mem_avg << std::endl;
        allf << "Max: cpu: " << cpu_max << "\t" << "mem: " << mem_max << std::endl;
    };

private:
    int pid_;
    std::string record_dir_;
    std::string identifier_;
};

}
#endif //BENXDB_STATUS_THREAD_H
